#ifndef A8_TCPCLIENT_H
#define A8_TCPCLIENT_H

#include <a8/netbase.hpp>

namespace a8
{
	class TcpClient
	{
	public:
		a8::Delegate<void (TcpClient*, int)> OnError;
		a8::Delegate<void (TcpClient*)> OnConnect;
		a8::Delegate<void (TcpClient*)> OnDisConnect;
		a8::Delegate<void (TcpClient*, char*, int)> OnSocketRead;
		std::string mRemoteAddress;
		int mRemotePort;

		TcpClient():mSocket(INVALID_SOCKET), mConnected(false), mRemoteAddress(""), mRemotePort(0), 
			mWorkerThread(NULL), mTopNode(NULL), mBotNode(NULL)
		{
		}

		virtual ~TcpClient()
		{
			Close();
		}

		void Open()
		{
			if(!IsActive()){
				SetActive(true);
			}
		}

		void Close()
		{
			if(IsActive()){
				SetActive(false);
			}
		}

		bool IsActive() 
		{ 
			return mSocket != INVALID_SOCKET; 
		}    

		bool Connected() 
		{ 
			return mConnected;
		}

		void SendBuff(const char* buff, unsigned int bufflen)
		{	
			if (bufflen > 0){
				SendQueueNode* p = (SendQueueNode*)malloc(sizeof(SendQueueNode));
				memset(p, 0, sizeof(SendQueueNode));
				p->mBuff = (char*)malloc(bufflen);
				memmove(p->mBuff, buff, bufflen);
				p->mBuffLen = bufflen;			
				mSendBufferMutex.Lock();
				if (mBotNode){
					mBotNode->mNext = p;
					mBotNode = p;
				}else{
					mTopNode = p;
					mBotNode = p;
				}
				mSendBufferMutex.UnLock();
			}
		}

	private:
		volatile SOCKET mSocket;
		bool mConnected;
		a8::Runnable* mWorkerThread;
		a8::sync::Cond mSendBufferCond;
		a8::sync::Mutex mSendBufferMutex;
		SendQueueNode *mTopNode;
		SendQueueNode *mBotNode;

		void SetActive(bool active) 
		{	 
			if(active){
				if(IsActive())
					return;
				if(!mWorkerThread){
					ActiveStop();
				}
				mWorkerThread = new a8::Runnable();
				mWorkerThread->OnExecute += std::make_pair(this, &TcpClient::WorkerThreadProc);
				mWorkerThread->Start();
			}else{
				if(mWorkerThread){
					ActiveStop();
				}
			}
		}

		bool ActiveStart()
		{
			mSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
			if(INVALID_SOCKET == mSocket){
				OnError(this, errno);
				return false;
			}
			sockaddr_in sa;
			memset(&sa, 0, sizeof(sa));
			sa.sin_family = AF_INET;
			sa.sin_addr.s_addr = inet_addr(mRemoteAddress.c_str());
			sa.sin_port = htons(mRemotePort);
			if(connect(mSocket, (sockaddr*)&sa, sizeof(sa)) < 0){
				OnError(this, errno);
				closesocket(mSocket);
				mSocket = INVALID_SOCKET;
				return false;
			}
			mConnected = true;
			OnConnect(this);
			return true;
		}

		void ActiveStop()
		{
			mConnected = false;
			if(mSocket != INVALID_SOCKET){
				shutdown(mSocket, 2);
				closesocket(mSocket);				
			}
			if(mWorkerThread){
				delete mWorkerThread;
				mWorkerThread = NULL;
			}
			mSocket = INVALID_SOCKET;
		}

		void WorkerThreadProc(a8::Runnable* thread)
		{
			if(!ActiveStart()){
				return;
			}

			a8::Runnable *senderthread = new a8::Runnable();
			senderthread->OnExecute += std::make_pair(this, &TcpClient::SenderThreadProc);
			senderthread->Start();
			char recvBuf[MAX_RECV_BUFFERSIZE];						
			while(!thread->IsShutdown()){
				int ret = ::recv(mSocket, recvBuf, MAX_RECV_BUFFERSIZE, 0);
				if(ret < 0){
					mConnected = false;
					OnDisConnect(this);
					mWorkerThread->Stop();
					break;
				}else if(ret == 0){
					mConnected = false;
					OnDisConnect(this);
					mWorkerThread->Stop();
					break;
				}else{
					OnSocketRead(this, recvBuf, ret);
				}
			}
			delete senderthread;
			mSocket = INVALID_SOCKET;
		}

		void SenderThreadProc(a8::Runnable* thread)
		{
			SendQueueNode *worknode = NULL, *currnode = NULL;			
			while(!thread->IsShutdown()){
				if(!worknode && mTopNode){
					mSendBufferMutex.Lock();
					worknode = mTopNode;
					mTopNode = NULL;
					mBotNode = NULL;
					mSendBufferMutex.UnLock();
				}
 
				while(worknode && !thread->IsShutdown()){
					currnode = worknode;
					while(currnode->mSentBytes < currnode->mBuffLen && !thread->IsShutdown()){
						int len = ::send(mSocket, currnode->mBuff + currnode->mSentBytes, 
										 currnode->mBuffLen - currnode->mSentBytes, 0);
						if(len > 0){
							currnode->mSentBytes += len;
						}else{
							break;
						}
					}
					//send
					if(currnode->mSentBytes >= currnode->mBuffLen){
						worknode = worknode->mNext;
						free(currnode->mBuff);
						free(currnode);
					}
				}
				mSendBufferCond.Wait(10);
			}
			while(worknode){
				currnode = worknode;
				worknode = worknode->mNext;
				free(currnode->mBuff);
				free(currnode);
			}
		}

	};
}

#endif
